﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace SchemeInterpreter
{
    class Program
    {

        private static FileInfo petitePathInformation;
        private static List<Thread> threads;

        private static string lastEvaluatedScratchpadFile;

        static void Main(string[] args)
        {

            threads = new List<Thread>();

            var identity = WindowsIdentity.GetCurrent();
            var principal = new WindowsPrincipal(identity);
            if (!principal.IsInRole(WindowsBuiltInRole.Administrator))
            {
                var currentProcess = Process.GetCurrentProcess();

                var information = new ProcessStartInfo();
                information.FileName = Path.Combine(Environment.CurrentDirectory, currentProcess.ProcessName + ".exe");
                information.Verb = "runas";

                Process.Start(information);
                return;
            }

            if (!File.Exists("petite-path"))
            {
                File.WriteAllText("petite-path", "");
            }

            var petitePath = File.ReadAllText("petite-path");
            if (string.IsNullOrEmpty(petitePath) || !File.Exists(petitePath))
            {
                while (!File.Exists(petitePath))
                {
                    Console.WriteLine("Write the full path to your petite.exe file, followed by pressing enter.");

                    petitePath = Console.ReadLine();
                    if (!File.Exists(petitePath))
                    {
                        Console.WriteLine("That path was not correct. Try again.");
                    }
                }

                File.WriteAllText("petite-path", petitePath);
            }

            var schemeFiles = Directory.GetFiles(Environment.CurrentDirectory, "*.scm");
            if (!schemeFiles.Any())
            {
                File.WriteAllText("scratchpad.scm", "\"Hello world from Scheme!\"");
                Process.Start("scratchpad.scm");
            }

            Thread.Sleep(100);

            lastEvaluatedScratchpadFile = schemeFiles.First();

            petitePathInformation = new FileInfo(petitePath);

            var watcher = new FileSystemWatcher();
            watcher.Changed += watcher_Changed;
            watcher.Path = Environment.CurrentDirectory;
            watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Attributes | NotifyFilters.Size;

            watcher.EnableRaisingEvents = true;

            Console.WriteLine("The scheme script file has been created (scratchpad.scm in the same directory as this program).\n");
            Console.WriteLine("We're ready! Leave this program running, and modify your script file as needed. Every time it is saved, it will be evaluated in Scheme here!\n");
            Console.WriteLine("You can hit enter if Scheme is stuck or is running forever, and it will be re-launched. You may also write Scheme code in the console window to evaluate it directly.\n");

            Console.ForegroundColor = ConsoleColor.White;
            Console.Write("> ");

            Console.ForegroundColor = ConsoleColor.Blue;

            string input = null;
            while (true)
            {

                EvaluateScheme(input);

                input = null;
                input = Console.ReadLine();

                lock (typeof(Program))
                {
                    Console.ForegroundColor = ConsoleColor.Green;
                    if (string.IsNullOrEmpty(input))
                    {
                        Console.WriteLine("You hit enter. An infinite loop has been prevented.\n");
                        KillScheme();

                        input = " ";
                    }
                    else
                    {
                        Console.WriteLine("\nYour input is being forwarded to Scheme.\n");
                    }
                    Console.ForegroundColor = ConsoleColor.Blue;
                }

            }
        }

        static string PrepareString(string input)
        {
            while (input.Contains("> >"))
            {
                input = input.Replace("> >", ">");
            }
            while (input.Contains("\n\n> "))
            {
                input = input.Replace("\n\n> ", "\n>");
            }
            return input;
        }

        static void EvaluateScheme(string schemeInput = null)
        {

            if (schemeInput != null)
            {
                schemeInput = schemeInput.Replace("\n", "").Replace("\r", "");
            }

            for (var i = threads.Count - 1; i >= 0; i--)
            {
                var currentThread = threads[i];
                currentThread.Abort();
                threads.RemoveAt(i);
            }

            KillScheme();

            Thread.Sleep(100);

            SaveCombined();

            Thread.Sleep(100);

            var thread = new Thread(delegate()
            {

                Thread.Sleep(100);

                var processStartInformation = new ProcessStartInfo(petitePathInformation.FullName);
                processStartInformation.CreateNoWindow = true;
                processStartInformation.UseShellExecute = false;
                processStartInformation.RedirectStandardError = true;
                processStartInformation.RedirectStandardInput = true;
                processStartInformation.RedirectStandardOutput = true;

                var newProcess = Process.Start(processStartInformation);

                if (!File.Exists(lastEvaluatedScratchpadFile) && string.IsNullOrEmpty(schemeInput))
                {
                    return;
                }

                var error = newProcess.StandardError;
                var output = newProcess.StandardOutput;

                var input = newProcess.StandardInput;

                var schemeData = File.ReadAllText(lastEvaluatedScratchpadFile) + ("\n\n" + schemeInput + "\n\n") + "\n(exit)\n";
                input.Write(schemeData);

                Thread.Sleep(100);

                lock (typeof(Program))
                {
                    var outputData = output.ReadToEnd();
                    lock (typeof(Program))
                    {
                        Console.ForegroundColor = ConsoleColor.White;
                        Console.Write(PrepareString(outputData));
                    }
                }

                lock (typeof(Program))
                {
                    var outputData = error.ReadToEnd();
                    outputData = outputData.Trim();

                    if (!string.IsNullOrEmpty(outputData))
                    {
                        lock (typeof(Program))
                        {
                            Console.ForegroundColor = ConsoleColor.Red;
                            Console.WriteLine("\n\n" + PrepareString(outputData) + "\n");

                            Console.ForegroundColor = ConsoleColor.White;
                            Console.Write("> ");
                        }
                    }
                }

                Console.ForegroundColor = ConsoleColor.Blue;

                KillScheme();

            });
            thread.Start();
            threads.Add(thread);
        }

        private static void KillScheme()
        {
            var processes = Process.GetProcessesByName("petite");
            foreach (var process in processes)
            {
                try
                {
                    process.Kill();
                }
                catch (Win32Exception)
                {
                }
                process.Dispose();
            }
        }

        static void SaveCombined()
        {
            var thread = new Thread(delegate()
            {
                Thread.Sleep(1000);

                try
                {
                    if (File.Exists("combined.scm"))
                    {
                        File.Delete("combined.scm");
                    }

                    var files = Directory.GetFiles(Environment.CurrentDirectory, "*.scm").OrderBy(f => f);
                    foreach (var file in files)
                    {
                        var filename = Path.GetFileNameWithoutExtension(file).Replace("scratchpad", "").Replace(" ", "").Replace("_", " ");
                        if (filename.StartsWith(" "))
                        {
                            filename = filename.Substring(1);
                        }

                        File.AppendAllText("combined.scm", ";;;;;;;;;;;;;;;; " + filename + " ;;;;;;;;;;;;;;;;\n\n" + File.ReadAllText(file) + "\n\n\n\n\n\n\n\n\n\n");
                    }
                }
                catch (Exception)
                {
                }
            });
            threads.Add(thread);
            thread.Start();
        }

        static void watcher_Changed(object sender, FileSystemEventArgs e)
        {
            if (e.FullPath.Contains(Path.Combine(Environment.CurrentDirectory, "scratchpad")) && e.FullPath.ToLower().EndsWith(".scm"))
            {
                if (!e.FullPath.Contains("!")) lastEvaluatedScratchpadFile = e.FullPath;
                EvaluateScheme();
            }
        }
    }
}
